import random
import cards
import human_player

from artificial_player1 import ArtificialPlayer1
from artificial_player2 import ArtificialPlayer2
from artificial_player3 import ArtificialPlayer3

class Game:
    players = []
    dealer_index = None
    community_cards = []
    current_bet = 0
    big_blind = 20
    small_blind = 10
    pot = 0
    hands_played = 0
    running = False
    
    def start(self):
        self.running = True
        self.dealer_index = random.randint(0, len(self.players) - 1)
        self.card_deck = cards.card_deck()
        
    def checkable(self, amount):
        return self.current_bet == amount
    
    def get_dealer(self):
        return self.players[self.dealer_index]
        
    def increment_dealer(self):
        self.dealer_index = (self.dealer_index + 1) % len(self.players)
    
    def small_blind_index(self):
        return (self.dealer_index + 1) % len(self.players)
    
    def big_blind_index(self):
        return (self.dealer_index + 2) % len(self.players)
        
    def create_and_add_player(self, name):
        player_mode = input("Is " + name + " artificial (1, 1b, 2, 2b, 3, 3b) or human (h): ").rstrip()
    
        if player_mode == "1":
            self.players.append(ArtificialPlayer1(self, name))
        elif player_mode == "1b":
            self.players.append(ArtificialPlayer1(self, name, 0.2))
        elif player_mode == "2":
            self.players.append(ArtificialPlayer2(self, name))
        elif player_mode == "2b":
            self.players.append(ArtificialPlayer2(self, name, 0.2))
        elif player_mode == "3":
            self.players.append(ArtificialPlayer3(self, name))
        elif player_mode == "3b":
            self.players.append(ArtificialPlayer3(self, name, 0.2))
        elif player_mode == "h":
            self.players.append(human_player.HumanPlayer(self, name))
        else:
            print("Unknown player mode. Try again.")
            self.create_and_add_player(name)
            
    def str_position(self, i):
        info = ""
        if i == self.dealer_index:
            info += " D"
        if i == self.small_blind_index():
            info += " SB"
        if i == self.big_blind_index():
            info += " BB"
        
        return info
    
    def clear_bets(self):
        [p.clear_bet() for p in self.players]
    
    def unfold_players(self):
        [p.unfold() for p in self.players]
    
    def add_to_pot(self, amount):
        self.pot += amount
    
    def print_player_info(self, current_player = None):
        for i in range(len(self.players)):
            p = self.players[i]
            
            if p.folded:
                status = "Folded"
            else:
                status = "Current bet: $" + str(p.current_bet)
            if p == current_player:
                arrow = "\t<---"
            else:
                arrow = ""
            
            print("%s: $%d\t%s\t%s%s" % (p, p.money, self.str_position(i), status, arrow))
    
    def play_pre_flop(self):
        for player in self.players:
            player.hole_cards = self.card_deck.deal_n_cards(2)
        
        print("\n*** PREFLOP ***")
        self.play_standard_round((self.dealer_index + 3) % len(self.players))
        
    def play_flop(self):
        self.new_round()
        self.community_cards = self.card_deck.deal_n_cards(3)
        print("\n*** FLOP ***")
        self.play_standard_round((self.dealer_index + 1) % len(self.players))
        
    def play_turn(self):
        self.new_round()
        self.community_cards.append(self.card_deck.deal_one_card())
        print("\n*** TURN ***")
        self.play_standard_round((self.dealer_index + 1) % len(self.players))
        
    def play_river(self):
        self.new_round()
        self.community_cards.append(self.card_deck.deal_one_card())
        print("\n*** RIVER ***")
        self.play_standard_round((self.dealer_index + 1) % len(self.players))
    
    def play_standard_round(self, start_player_index):
        first_round = True
        player_index = start_player_index
        
        while first_round or (not self.round_finished()):
            player = self.players[player_index]
            
            if not player.folded:
                print() # newline
                self.print_turn_info(player)
                player.do_move()
            
            player_index = (player_index + 1) % len(self.players)
            
            # back to first player
            if first_round and player_index == start_player_index:
                first_round = False
            
            # are we finished?
            if len(self.players_in_hand()) == 1:
                break
            
        for p in self.players_in_hand():
            p.activity[len(self.community_cards)] += 1
            #print(p.activity)
        
    def round_finished(self):       
        for player in self.players_in_hand():
            if player.current_bet != self.current_bet:
                return False
        
        return True
            
    def print_turn_info(self, player):
        self.print_player_info(player)
        
        print() # newline
        print("Pot: $" + str(self.pot))
        print()
        if self.community_cards:
            print("Community cards: " + cards.card_names(self.community_cards))
            print()

    # new betting round
    def new_round(self):
        self.current_bet = 0
        self.clear_bets()

    def new_hand(self):
        self.hands_played += 1
        # shuffle the cards
        self.card_deck.reset()
        self.community_cards = []
        self.unfold_players()
        self.new_round()
        self.clear_temp_logs()
        self.increment_dealer()
        self.pot = 0
        self.current_bet = self.big_blind
        self.players[self.small_blind_index()].pay_blind(self.small_blind)
        self.players[self.big_blind_index()].pay_blind(self.big_blind)
        
        [print(str(p) + ": " + str(p.context_log)) for p in self.players]
        
        [print(str(p) + ": " + str(p.activity)) for p in self.players]
        
        print("\n****** NEXT HAND ******")
        
    def clear_temp_logs(self):
        [p.clear_temp_log() for p in self.players]
        
    def min_raise(self):
        return self.current_bet * 2
    
    def max_raise(self):
        return 500 * self.big_blind
    
    def players_in_hand(self, exclude=None):
        return [p for p in self.players if (not p.folded and not p == exclude)]
    
    def player_activity(self, exclude=None):
        return [p.relative_activity() for p in self.players_in_hand() if not p == exclude]
    
    def player_propability_context(self, exclude=None):
        return [p.context_probability() for p in self.players_in_hand() if not p == exclude]
    
    def play_next_hand(self):
        self.new_hand()
        
        self.play_pre_flop()
        
        if len(self.players_in_hand()) > 1:
            self.play_flop()
        if len(self.players_in_hand()) > 1:
            self.play_turn()
        if len(self.players_in_hand()) > 1:
            self.play_river()
        
        if len(self.community_cards) == 5 and len(self.players_in_hand()) > 1:
            print("\n*** SHOWDOWN ***\n")
            print("Community cards: " + cards.card_names(self.community_cards))
            print()
            self.showdown()
            
        self.pay_winner()
        [print(str(p) + ": " + str(p.activity)) for p in self.players]
        [print(str(p) + ": calls " + str(p.calls) + " raises: " + str(p.raises)) for p in self.players]
    
    def showdown(self):
        for p in self.players_in_hand():
            print(str(p) + " shows " + cards.card_names(p.hole_cards) + " -- " + cards.str_power(p.get_hand_power()))
            p.store_temp_log()
    
    def winner(self):
        """ Sorts the list of players based on their hand power and
            returns the player having the largest one (which will be the last in the sorted list) """
            
        return sorted(self.players_in_hand(), key = lambda p: p.get_hand_power()).pop()
    
    def pay_winner(self):
        winner = self.winner()
        winner.money += self.pot
        print()
        print(str(winner) + " wins $" + str(self.pot) + ".")

def main():
    game = Game()

    print("Welcome to the poker simulator.")
    num_players = int(input("Number of players: "))
    
    for i in range(1, num_players + 1):
        game.create_and_add_player("Player " + str(i))
    
    game.start()
    
    while game.running and game.hands_played <= 1000:
        game.play_next_hand()
        print("Hands Played: " + str(game.hands_played))

main()